'''
Created on 13/05/2009

@author: Edward
'''
from yarra.datastore.identitymap import IdentityMap,\
    IdentityMapNotFoundException

class DSCache(object):
    """ 
    The Datastore Cache is responsible for storing the results of a query in memory, to reduce 
    the number of queries to the database. Hence repeated calls for the same data will be served by the
    cache as opposed to the database. The Datastore Cache achieves this by using the IdentityMap to 
    store the dsos returned from a query against the dso's id and version number. To satisfy queries
    other than by id and version, the datastore cache also maintains a map between other dso attributes (e.g. URI)
    and (id, version).
    """
    
    def __init__(self):
        self.__identity_map = IdentityMap()
        self.__latest_version = {}
        self.__versions = {}
        self.__uri_version = {}
        self.__uris = {}
        
    def save(self, dsos):
        """ called to add the newly saved dsos to cache """
        for dso in dsos:
            if dso.id is None or dso.version is None:
                raise DSCacheException('DSO does not have id or version')
        for dso in dsos:
            self._add_dso_to_identity_map(dso)
            self._add_dso_as_latest_version(dso)
            self._add_dso_by_uri_and_version(dso)
            
            # track the versions of a dso
            try:
                self.__versions[dso.id].append(dso.version)
            except KeyError:
                # don't create a new list and add this version as we don't know about the other versions
                # that exist to create a complete picture in the cache
                pass
            
            #  track by uris
            try:
                self.__uris[dso.uri].add((dso.id, dso.version))
            except KeyError:
                # don't create a new set and add this version as we don't know about the other dsos 
                # that have this uri to create a complete picture in the cache
                pass
                
        return dsos
    
    def find_by_id_and_version(self, id, version):
        try:
            return self.__identity_map.get_dso(id, version)
        except IdentityMapNotFoundException:
            raise DSCacheNotFoundException()
    
    def set_by_id_and_version(self, dso):
        """ called to save the result of a find by id and version to the cache """
        if dso.id is None or dso.version is None:
                raise DSCacheException('DSO does not have id or version')
        self._add_dso_to_identity_map(dso)
        self._add_dso_by_uri_and_version(dso)
        return dso
                
    def find_by_id(self, id):
        try:
            dso_latest_version = self.__latest_version[id]
        except KeyError:
            raise DSCacheNotFoundException('DSO id is not in cache')
        return self.__identity_map.get_dso(id, dso_latest_version)        
    
    def set_by_id(self, dso):
        """ called to save the result of a find by ids to the cache """
        if dso.id is None or dso.version is None:
                raise DSCacheException('DSO does not have id or version')
        self._add_dso_to_identity_map(dso)
        self._add_dso_by_uri_and_version(dso)
        self._add_dso_as_latest_version(dso)
        return dso
                         
    def find_by_URI_and_version(self, uri, version):
        try:
            (dso_id, dso_version) = self.__uri_version[(uri, version)]
            return self.__identity_map.get_dso(dso_id, dso_version)
        except KeyError:
            raise DSCacheNotFoundException()
    
    def set_by_URI_and_version(self, dso):
        """ called to save the result of a find by URI and version to the cache """
        if dso.id is None or dso.version is None:
                raise DSCacheException('DSO does not have id or version')
        self._add_dso_to_identity_map(dso)
        self._add_dso_by_uri_and_version(dso)
        return dso
           
    def find_by_URI(self, uri):
        dsos = []
        try:
            set_of_keys = self.__uris[uri]
        except KeyError:
            raise DSCacheNotFoundException('URI not found in cache, go to database')
        for (dso_id, dso_version) in set_of_keys:
            dso = self.__identity_map.get_dso(dso_id, dso_version)
            dsos.append(dso)
        return dsos
    
    def set_by_URI(self, dso):
        """ called to save the result of a find by URI """
        if dso.id is None or dso.version is None:
                raise DSCacheException('DSO does not have id or version')
        self._add_dso_to_identity_map(dso)
        self._add_dso_by_uri_and_version(dso)
        
        #  track by uris
        try:
            self.__uris[dso.uri].add((dso.id, dso.version))
        except KeyError:
            self.__uris[dso.uri] = set([(dso.id, dso.version)])
            
        return dso     
            
    def find_versions(self, id):
        try:
            return self.__versions[id]
        except KeyError:
            raise DSCacheNotFoundException('Go to database')

    def set_versions(self, id, versions_list):
        self.__versions[id] = versions_list
    
    def clear_cache(self):
        self.__identity_map = IdentityMap()
        self.__latest_version = {}
        self.__versions = {}
        self.__uri_version = {}
        self.__uris = {}

    def _add_dso_to_identity_map(self, dso):
        # first add to the identity map
        self.__identity_map.add_dso(dso)

    def _add_dso_as_latest_version(self, dso):
        # track the latest version of a dso
        self.__latest_version[dso.id] = dso.version

    def _add_dso_by_uri_and_version(self, dso):
        # track dso by uri and version
        self.__uri_version[(dso.uri, dso.version)] = (dso.id, dso.version)
        
class DSCacheException(Exception):
    pass

class DSCacheNotFoundException(DSCacheException):
    pass